@page "/link_track/link/info/{contextId}"
@using FS.LinkTrack
@using FS.Core.LinkTrack
@using FS.Extends
@using Newtonsoft.Json
@using FS.Core.Http
@using System.Text
@using System.Web
@using FS.LinkTrack.Dal
@inject IIocManager _iocManager;
@inject NavigationManager nav;
@inject IJSRuntime js;

<div class="layui-card">
    <div class="layui-card-header" id="anchor-play-header">
        @if (_lst.Count > 0)
        {
            <span>应用ID：@(_lst[0].AppId)，</span>
            <span>内容类型：@(_lst[0].ContentType)，</span>
            <span>请求时间：@(_lst[0].StartTs.ToTimestamps().ToString("yyyy-MM-dd HH:mm:ss"))，</span>
            @if (_lst[0].UseTs >= 100)
            {
                <span>整体耗时：<strong class="layui-badge layui-bg-red">@(_lst[0].UseTs) ms</strong></span>
            }
            else if (_lst[0].UseTs >= 50)
            {
                <span>整体耗时：<strong class="layui-badge layui-bg-blue">@(_lst[0].UseTs) ms</strong></span>
            }
            else if (_lst[0].UseTs > 0)
            {
                <span>整体耗时：@(_lst[0].UseTs) ms</span>
            }
        }
        <div class="layui-right">
            <button class="layui-btn" @onclick="GotoList"><i class="am-icon-plus"></i> 列表</button>
        </div>
    </div>
    @if (_lst.Count > 0)
    {
        <div class="layui-card-header" id="anchor-play-header">
            <span>
                <strong class="layui-badge layui-bg-blue">@_lst[0].RequestIp</strong>
                @for (int i = 0; i < _lst.Count; i++)
                {
                    var info = _lst[i];
                    <strong class="layui-badge layui-bg-green">@(_lst[i].Method) => </strong>
                    <a href="javascript:;" @onclick="o => ShowBody(info)"> @(_lst[i].Path)</a>
                }
            </span>
            <button class="layui-btn layui-btn-sm" @onclick="Request"><i class="am-icon-plus"></i>模拟请求</button>
        </div>
    }
    <div class="layui-card-body">
        <div class="layui-form layui-border-box layui-table-view" lay-filter="LAY-table-1" style=" ">
            <div class="layui-table-box" style="white-space:nowrap">
                <div style="margin: 0 10px;height: 20px;">
                    <ul>
                        @{ var preStartTs = 0L; }
                        @foreach (var startTs in lstVo.Select(o => o.StartTs).Distinct().OrderBy(o => o))
                        {
                            if (lstVo[0].UseTs >= 20 && preStartTs > 0 && startTs - preStartTs < 3)
                            {
                                preStartTs = startTs;
                                continue;
                            }
                            <li style="display:inline-block;float:left;position:absolute;margin-left:@(startTs / totalUse * 100)%">@startTs ms</li>
                            {
                                preStartTs = startTs;
                            }
                        }
                    </ul>
                </div>
                @foreach (var linkTrackVO in lstVo)
                {
                    <div class="layui-progress layui-progress-big" style="margin:10px;">
                        <div lay-tips="@linkTrackVO.Desc" class="layui-progress-bar" style="margin-left:@(linkTrackVO.StartTs / totalUse * 100)%;width:@(linkTrackVO.UseTs / totalUse * 100)%;background-color: rgba(@linkTrackVO.Rgba)"></div>
                        <span class="layui-progress-text" style="margin-left:@(linkTrackVO.StartTs / totalUse * 100)%;color:#000">
                            <strong class="layui-badge layui-bg-green" lay-tips="时间轴：@linkTrackVO.StartTs ms">@linkTrackVO.AppId</strong>
                            <span lay-tips="@linkTrackVO.Desc">@linkTrackVO.Caption</span>
                                <span style="color:#A0522D">@linkTrackVO.CsharpCallMethod</span>
                            @if (linkTrackVO.UseTs >= 100)
                            {
                                <strong class="layui-badge layui-bg-red"> 耗时：@(linkTrackVO.UseTs) ms</strong>
                            }
                            else if (linkTrackVO.UseTs >= 50)
                            {
                                <strong class="layui-badge layui-bg-blue"> 耗时：@(linkTrackVO.UseTs) ms</strong>
                            }
                            else if (linkTrackVO.UseTs > 0)
                            {
                                @(" 耗时：" + linkTrackVO.UseTs + " ms")
                            }
                        </span>
                    </div>
                }
                <div class="layui-table-header">
                    <table cellspacing="0" cellpadding="0" border="0" class="layui-table" lay-skin="auto" lay-size="sm">
                        <thead>
                        <tr>
                            <th data-field="ID">
                                <div class="layui-table-cell laytable-cell-14-0-0">
                                    <span>时间轴</span>
                                </div>
                            </th>
                            <th data-field="ID">
                                <div class="layui-table-cell laytable-cell-14-0-1">
                                    <span>埋点</span>
                                </div>
                            </th>
                            <th data-field="ID">
                                <div class="layui-table-cell laytable-cell-14-0-2">
                                    <span>调用链</span>
                                </div>
                            </th>
                        </tr>
                        </thead>
                    </table>
                </div>
                <div class="layui-table-body layui-table-main">
                    <table cellspacing="0" cellpadding="0" border="0" class="layui-table" lay-skin="auto" lay-size="sm">
                        <tbody>
                        @foreach (var linkTrackVO in lstVo)
                        {
                            <tr>
                                <td>
                                    <div class="layui-table-cell laytable-cell-14-0-0">@linkTrackVO.StartTs ms</div>
                                </td>
                                <td>
                                    <div class="laytable-cell-14-0-1" lay-tips="@linkTrackVO.Desc">
                                        <strong class="layui-badge layui-bg-green" lay-tips="时间轴：@linkTrackVO.StartTs ms">@linkTrackVO.AppId</strong> @linkTrackVO.Caption
                                        @if (linkTrackVO.UseTs >= 100)
                                        {
                                            <strong class="layui-badge layui-bg-red">耗时：@(linkTrackVO.UseTs) ms</strong>
                                        }
                                        else if (linkTrackVO.UseTs >= 50)
                                        {
                                            <strong class="layui-badge layui-bg-blue">耗时：@(linkTrackVO.UseTs) ms</strong>
                                        }
                                        else if (linkTrackVO.UseTs > 0)
                                        {
                                            @("耗时：" + linkTrackVO.UseTs + " ms")
                                        }
                                    </div>
                                </td>
                                <td>
                                    <div class="laytable-cell-14-0-2">
                                        @linkTrackVO.Desc
                                    </div>
                                </td>
                            </tr>
                        }
                        </tbody>
                    </table>
                </div>
            </div>
        </div>
    </div>
</div>
<style>
    .laytable-cell-14-0-0{ width: 130px; }
    .laytable-cell-14-0-1{ width: 900px; }
    .laytable-cell-14-0-2{ width: 500px; }
</style>

@code{

    [Parameter]
    public string ContextId { get; set; }
    private List<LinkTrackContextPO> _lst = new();

    protected override async Task OnParametersSetAsync()
    {
        await GetList();
    }

    private readonly List<LinkTrackVO> lstVo = new();
    private double totalUse;

    private async Task GetList()
    {
        _lst = await LinkTrackEsContext.Data.LinkTrackContext.Asc(d => d.StartTs)
                                       .Where(t => t.ContextId == ContextId).ToListAsync();

        if (_lst.Count == 0) GotoList();

        var startTs = _lst[0].StartTs;
        totalUse = _lst[0].UseTs * 1.5;


        AddLinkTrack(_lst[0], startTs);
        lstVo.Add(new LinkTrackVO
        {
            Rgba = LinkTrackVO.RgbaList[0],
            AppId = _lst[0].AppId,
            StartTs = _lst[0].EndTs - startTs,
            UseTs = -1,
            Caption = $"调用完成",
            Desc = $"耗时：{_lst[0].UseTs} ms"
        });
    }

    private int rgbaIndex = -1;

    private void AddLinkTrack(LinkTrackContextPO linkTrackContextPO, long startTs)
    {
        rgbaIndex++;
        lstVo.Add(new LinkTrackVO
        {
            Rgba = LinkTrackVO.RgbaList[rgbaIndex],
            AppId = linkTrackContextPO.AppId,
            StartTs = linkTrackContextPO.StartTs - startTs,
            UseTs = linkTrackContextPO.UseTs,
            Caption = $"{linkTrackContextPO.RequestIp} 【{linkTrackContextPO.Method}】=> {linkTrackContextPO.Path} ",
            Desc = $"{linkTrackContextPO.Domain} {linkTrackContextPO.ContentType} 客户端IP：{linkTrackContextPO.RequestIp}"
        });
        foreach (var linkTrackDetail in linkTrackContextPO.List)
        {
            var firstStackTrace = linkTrackDetail.CallStackTrace;
            var linkTrackVO = new LinkTrackVO
            {
                Rgba = LinkTrackVO.RgbaList[rgbaIndex],
                AppId = linkTrackContextPO.AppId,
                StartTs = linkTrackDetail.StartTs - startTs,
                UseTs = linkTrackDetail.UseTs,
                Desc = "",
                CsharpCallMethod = firstStackTrace != null ? $"{firstStackTrace.ReturnType} {firstStackTrace.CallMethod}({string.Join(",", firstStackTrace.MethodParams.Select(oo => $"{oo.Value} {oo.Key}"))}) {firstStackTrace.FileLineNumber}" : "",
            };

            switch (linkTrackDetail.CallType)
            {
                case EumCallType.HttpClient:
                    linkTrackDetail.Data.TryGetValue("Url", out var url);
                    linkTrackDetail.Data.TryGetValue("Method", out var method);
                    linkTrackDetail.Data.TryGetValue("RequestBody", out var requestBody);
                    linkTrackDetail.Data.TryGetValue("Header", out var header);
                    linkTrackDetail.Data.TryGetValue("ResponseBody", out var responseBody);
                    linkTrackVO.Desc = $"头部：{header}，入参：{requestBody}，响应：{responseBody} ";
                    linkTrackVO.Caption = $"调用{linkTrackDetail.CallType.ToString()} => [{method}] {url} ";
                    lstVo.Add(linkTrackVO);
                    var httpTrackContextPO = _lst.Find(o => o.ParentAppId == linkTrackContextPO.AppId);
                    if (httpTrackContextPO != null)
                    {
                        AddLinkTrack(httpTrackContextPO, startTs);
                    }
                    break;
                case EumCallType.GrpcClient:
                    linkTrackDetail.Data.TryGetValue("Server", out var server);
                    linkTrackDetail.Data.TryGetValue("Action", out var action);
                    linkTrackVO.Caption = $"调用{linkTrackDetail.CallType.ToString()}服务 => {server}/{action} ";
                    lstVo.Add(linkTrackVO);
                    var grpcTrackContextPO = _lst.Find(o => o.ParentAppId == linkTrackContextPO.AppId);
                    if (grpcTrackContextPO != null)
                    {
                        AddLinkTrack(grpcTrackContextPO, startTs);
                    }
                    break;
                case EumCallType.Database:
                    if (linkTrackDetail.Data.ContainsKey("Sql"))
                    {
                        linkTrackVO.Desc = $"{linkTrackDetail.Data["Sql"]} ";
                        linkTrackVO.Caption = $"执行数据库 => {linkTrackDetail.Data["DataBaseName"]} {linkTrackDetail.Data["TableName"]}.{linkTrackDetail.CallMethod} ";
                    }
                    else if(linkTrackDetail.Data.ContainsKey("ConnectionString"))
                    {
                        linkTrackVO.Desc = $"{linkTrackDetail.Data["ConnectionString"]} ";
                        linkTrackVO.Caption = $"数据库操作 => {linkTrackDetail.CallMethod} ";
                    }
                    else
                    {
                        linkTrackVO.Caption = $"数据库操作 => {linkTrackDetail.CallMethod} ";
                    }
                    lstVo.Add(linkTrackVO);
                    break;
                case EumCallType.Redis:
                    linkTrackDetail.Data.TryGetValue("RedisKey", out var redisKey);
                    linkTrackDetail.Data.TryGetValue("RedisHashFields", out var redisHashFields);
                    linkTrackVO.Caption = $"执行Redis => {linkTrackDetail.CallMethod} {redisKey} {redisHashFields} ";
                    linkTrackVO.Desc = $"{linkTrackDetail.CallMethod} {redisKey} {redisHashFields}";
                    lstVo.Add(linkTrackVO);
                    break;
                case EumCallType.Mq:
                    linkTrackVO.Caption = $"发送MQ消息 => {linkTrackDetail.CallMethod} ";
                    lstVo.Add(linkTrackVO);
                    break;
                case EumCallType.Elasticsearch:
                    linkTrackVO.Caption = $"执行ES => {linkTrackDetail.CallMethod} ";
                    lstVo.Add(linkTrackVO);
                    break;
                case EumCallType.Custom:
                    linkTrackDetail.Data.TryGetValue("Message", out var message);
                    linkTrackVO.Caption = $"手动埋点 => {message} ";
                    lstVo.Add(linkTrackVO);
                    break;
            }
        }
        rgbaIndex--;
    }

    // 显示报文详情
    private async Task ShowBody(LinkTrackContext info)
    {
        var lstContent = new List<string>();
        var useTsHtml = $"<span>整体耗时：{info.UseTs} ms</span>";
        if (info.UseTs >= 100)
        {
            useTsHtml = $"<span>整体耗时：<strong class='layui-badge layui-bg-red'>{info.UseTs} ms</strong></span>";
        }
        else if (info.UseTs >= 50)
        {
            useTsHtml = $"<span>整体耗时：<strong class='layui-badge layui-bg-blue'>{info.UseTs} ms</strong></span>";
        }

        lstContent.Add($"<pre class='layui-code layui-box layui-code-view' style='font-size: 14px;'>{info.StartTs.ToTimestamps():yyyy-MM-dd HH:mm:ss} <strong class='layui-badge layui-bg-green'>{info.ContentType}</strong> <strong class='layui-badge layui-bg-blue'>{info.RequestIp}</strong> <strong class='layui-badge layui-bg-green'>{info.Method} =></strong> {info.Path} {useTsHtml}</pre>");
        if (info.Headers.Count > 0)
        {
            lstContent.Add($"<pre class='layui-code layui-box layui-code-view' style='font-size: 14px;'><strong class='layui-badge layui-bg-green'>Header：</strong>{JsonConvert.SerializeObject(info.Headers)}</pre>");
        }
        if (!string.IsNullOrWhiteSpace(info.RequestBody))
        {
            lstContent.Add($"<pre class='layui-code layui-box layui-code-view' style='font-size: 14px;'><strong class='layui-badge layui-bg-green'>入参：</strong>{HttpUtility.HtmlEncode(info.RequestBody)}</pre>");
        }
        if (!string.IsNullOrWhiteSpace(info.ResponseBody))
        {
            lstContent.Add($"<pre class='layui-code layui-box layui-code-view' style='font-size: 14px;'><strong class='layui-badge layui-bg-green'>出参：</strong>{HttpUtility.HtmlEncode(info.ResponseBody)}</pre>");
        }

        await js.InvokeVoidAsync("layer.open", new
        {
            type = 1,
            skin = "layui-layer-molv",
            closeBtn = false,
            area = "1500px",
            anim = 10,
            shadeClose = true,
            title = $"{info.AppId} {info.ContextId}",
            content = string.Join("", lstContent)
        });
    }

    private void GotoList()
    {
        nav.NavigateTo("/link_track/link/apiserver");
    }

    private async Task Request()
    {
        var load = await js.InvokeAsync<int>("layer.load", 0, "{shade: false}");
        try
        {
            var info = _lst[0];
            var result = "";
            var header = info.Headers;
            header.Remove("Connection");
            header.Remove("Accept");
            header.Remove("Accept-Language");
            header.Remove("Accept-Encoding");
            header.Remove("Authorization");
            header.Remove("Host");
            header.Remove("Referer");
            header.Remove("User-Agent");
            header.Remove("Cache-Control");
            header.Remove("Pragma");
            header.Remove("Content-Length");
            header.Remove("Content-Type");

            if (info.ContentType.Contains("json") && string.IsNullOrWhiteSpace(info.RequestBody)) info.RequestBody = "{}";

            var linkTrackContext = FsLinkTrack.Current.Get();
            var linkTrackContextPO = new LinkTrackContextPO
            {
                ContextId = linkTrackContext.ContextId,
                AppId = linkTrackContext.AppId,
                ParentAppId = linkTrackContext.ParentAppId,
                StartTs = DateTime.Now.ToTimestamps(),
                Domain = info.Domain,
                Path = info.Path,
                Method = info.Method,
                Headers = header,
                ContentType = info.ContentType,
                RequestBody = info.RequestBody,
                RequestIp = "localhost",
            };

            switch (info.Method)
            {
                case "POST":
                    linkTrackContextPO.ResponseBody = await HttpPost.PostAsync(info.Path, info.RequestBody, header, info.ContentType);
                    break;
                case "GET":
                    linkTrackContextPO.ResponseBody = await HttpGet.GetAsync(info.Path, info.RequestBody, header);
                    break;
            }
            linkTrackContextPO.EndTs = DateTime.Now.ToTimestamps();
            await ShowBody(linkTrackContextPO);
        }
        catch (Exception e)
        {
            await js.InvokeVoidAsync("layer.alert", e.Message, new
            {
                icon = 2,
                title = e.Message
            });
        }
        await js.InvokeVoidAsync("layer.close", load);
    }
}